#!/usr/bin/env xcrun swift


//  Created by CeriNo on 16/7/25.
//  Copyright © 2016年 AppCan. All rights reserved.
import Foundation

/// XcodeData是一个key为String,value为String/Array/Dicstionary的Dictionary
typealias XcodeData = [String: AnyObject]

/// XcodeDataTuple 是由XcodeData及它的uuid构成的tuple
typealias XcodeDataTuple = (uuid: String,data: XcodeData)

// MARK: - 随机UUID生成器
// XcodeData所用的UUID是长度为24的由16进制字符构成的字符串
struct UUIDGenerator {
    // MARK: 生成一个随机UUID
    /**
     *
     - returns: 生成的UUID
     */
    static func newUUID() -> String {
        let uuid: NSString = NSUUID().UUIDString.stringByReplacingOccurrencesOfString("-", withString: "")
        return uuid.substringToIndex(24) as String
    }
}


// MARK: - Xcode工程中的Group
class Group {
    /// group所在的工程
    let project: Project
    /// group的data
    var data: XcodeData
    /// group的uuid
    let uuid: String
    /// group的children,由"children"字段决定,是一个由uuid构成的数组
    var children: [String]{
        get{
            guard let children = data["children"] as? [String] else{
                print("invalid group! no children found!")
                fatalError()
            }
            return children
        }
        set{
            data["children"] = newValue
        }
    }
    
    init(project: Project,data: XcodeData,uuid: String){
        self.project = project
        self.data = data
        self.uuid = uuid
    }
    
    
    /**
     搜索当前group,返回指定名字的subgroup,返回值可能为空
     
     - parameter name: subgroup的name
     
     - returns: subgroup或者nil
     */
    func subgroup(withName name:String) -> Group? {
        let subgroups:[XcodeDataTuple] = children
            //获得此group的所有children的uuid和data
            .flatMap{ uuid in
                if let data = project.objects[uuid] as? XcodeData{
                    return (uuid,data)
                }
                return nil
            }
            //其中subgroup的data的"isa"字段为"PBXVariantGroup"或者"PBXGroup"
            .filter{ (uuid,data) in
                if let isa = data["isa"] as? String where isa == "PBXVariantGroup" || isa == "PBXGroup"{
                    return true
                }
                return false
            }
            //data的"name"字段或者"path"字段决定了此group的名字
            .filter{ (uuid,data) in
                if let groupName = data["name"] as? String where groupName == name {
                    return true
                }
                if let groupPath = data["path"] as? String where groupPath == name{
                    return true
                }
                return false
        }
        //返回得到的group,可能为空
        guard let (uuid,data) = subgroups.first else{
            return nil
        }
        return Group(project: project, data: data, uuid: uuid)
    }
    
    
}

// MARK: - Xcode工程
class Project {
    //project的URL
    let URL: NSURL
    //project的pbxproj数据
    var data: XcodeData
    //pbxproj数据中的"objects"字段对应的字典
    var objects: XcodeData{
        get{
            guard let objs = data["objects"] as? XcodeData else{
                print("project.pbxproj invalid data: objects not found!")
                fatalError()
            }
            return objs
        }
        set{
            data["objects"] = newValue
        }
    }
    
    init(projectURL :NSURL){
        URL = projectURL
        let pbxData = projectURL.URLByAppendingPathComponent("project.pbxproj")
        guard NSFileManager.defaultManager().fileExistsAtPath(pbxData.path ?? "") else{
            print("project.pbxproj not found!")
            fatalError()
        }
        guard let dict = NSDictionary(contentsOfURL: pbxData) as? [String: AnyObject] else{
            print("project.pbxproj invalid data!")
            fatalError()
        }
        data = dict
    }
    
    /**
     保存更改后的数据
     */
    func save() -> Void {
        do{
            //将data以plist-xml格式直接写入.pbxproj文件中
            let plistData = try NSPropertyListSerialization.dataWithPropertyList(data, format: .XMLFormat_v1_0, options: 0)
            try plistData.writeToURL(URL.URLByAppendingPathComponent("project.pbxproj"), options: .DataWritingAtomic)
        }catch let e as NSError{
            print("save project failed:\(e.localizedDescription)")
            fatalError()
        }
    }
}

// MARK: - Project中的根组件
extension Project {
    /**
     pbxproject数据中的rootObject
     
     1. 先找data中的"rootObject"字段,获得rootObject的uuid
     2. 再根据此uuid获得rootObject
     
     - returns: rootObject数据
     */
    func rootObject() -> XcodeData {
        guard let rootObjectKey = data["rootObject"] as? String,
            rootObject = objects[rootObjectKey] as? XcodeData else{
                print("invalid project: rootObject not found!")
                fatalError()
        }
        return rootObject
    }
    
    /**
     pbxproject数据中的mainGroup
     
     1. 从rootObject中根据字段"mainGroup"获得mainGroup的的UUID
     2. 从工程的objects中根据此UUID获得mainGroup的data
     - returns: pbxproject的mainGroup
     */
    func mainGroup() -> Group {
        guard let mainGroupUUID = self.rootObject()["mainGroup"] as? String,
            data = objects[mainGroupUUID] as? XcodeData else{
                print("mainGroup not found!")
                fatalError()
        }
        return Group(project: self, data: data, uuid: mainGroupUUID)
        
    }
    
}

// MARK: - Project中的buildPhase
extension Project {
    /**
     buildphase 的isa类型
     
     - PBXFrameworksBuildPhase: "Frameworks" 对应的isa
     - PBXCopyFilesBuildPhase:  "Copy Files"类型的build pahse 对应的isa
     */
    enum buildPhaseIsa: String {
        case PBXFrameworksBuildPhase
        case PBXCopyFilesBuildPhase
    }
    
    /**
     获得prxproj数据中指定target的指定buildphase
     
     - parameter target: target名称
     - parameter isa:    builsphase的isa类型
     
     - returns: 此buildphase的uuid和data
     */
    private func buildPhases(targetName targetName: String,isaType: buildPhaseIsa) -> [XcodeDataTuple] {
        
        /// 从rootObject的"target"字段获取所有target的UUID
        guard let targetUUIDs = self.rootObject()["targets"] as? [String] else{
            print("invalid data: no target found!")
            return []
        }
        
        let targetData: [XcodeData] = targetUUIDs
            // 从objects中根据targetUUID获得target的data
            .flatMap{ targetUUID in
                if let targetData = objects[targetUUID] as? XcodeData{
                    return targetData
                }
                return nil
            }
            //根据data的"name"字段,只保留指定名字的targetData
            .filter{
                if let name = $0["name"] as? String where name == targetName{
                    return true
                }
                return false
        }
        
        //从targetdata的"buildPhase"字段获得这些targetData的所有buildPhase的UUID
        let buildPhaseUUIDs: [String] = targetData.reduce([]){
            if let uuids = $1["buildPhases"] as? [String]{
                return $0 + uuids
            }
            return $0
        }
        
        let buildPhases: [XcodeDataTuple] = buildPhaseUUIDs.flatMap{ uuid in
            //从objects中根据uuid获得buildPhase的data
            //根据data的"isa"字段过滤掉不符合条件的buildPhase
            if let data = objects[uuid] as? XcodeData,isa = data["isa"] as? String where isa == isaType.rawValue {
                return (uuid,data)
            }
            return nil
        }
        return buildPhases
    }
    
    /**
     返回指定target的frameworksBuildPhase
     
     - parameter targetName: target的名字
     
     - returns: frameworksBuildPhase的uuid和data
     */
    func frameworksBuildPhase(inTarget targetName: String) -> XcodeDataTuple? {
        //frameworksBuildPhase是唯一的,直接返回第一个
        return self.buildPhases(targetName: targetName, isaType: .PBXFrameworksBuildPhase).first
    }
    
    func embedFrameworksBuildPhase(inTarget targetName: String) -> XcodeDataTuple? {
        //name 为"Embed Frameworks"的copyFilesBuildPhase才是embedFrameworksBuildPhase
        return self.buildPhases(targetName: targetName, isaType: .PBXCopyFilesBuildPhase)
            .filter{ (uuid,data) in
                if let name = data["name"] as? String where name == "Embed Frameworks" {
                    return true
                }
                return false
            }
            .first
    }
    
}



// MARK: - 主任务,添加动态framework的task
class AddEmbedFrameworkTask {
    let fileMgr = NSFileManager.defaultManager()
    // 当前目录的URL "./"
    let currentURL: NSURL = NSURL.fileURLWithPath(NSFileManager.defaultManager().currentDirectoryPath)
    // XcodeProject实例,待定
    var project: Project? = nil
    //要添加的framework名字
    var frameworkName = "Charts.framework"
    
    
    // MARK: 复制测试用的资源文件
    func copyResources() -> Void {
        print("copy task resoueces...")
        do{
            //资源文件夹路径 "./origin"
            let originURL = currentURL.URLByAppendingPathComponent("origin"),
            //测试用文件夹路径 "./test"
            testURL = currentURL.URLByAppendingPathComponent("test")
            if fileMgr.fileExistsAtPath(testURL.path ?? ""){
                //如果测试用文件夹已存在,先删除
                try fileMgr.removeItemAtURL(testURL)
            }
            //复制资源文件夹至测试用文件夹
            try fileMgr.copyItemAtURL(originURL, toURL: testURL)
        }catch let e as NSError{
            print("copy resources error:\(e.localizedDescription)!")
            fatalError()
        }
    }
    
    
    
    // MARK: 加载XcodeProject
    func loadProject() -> Void {
        print("load project...")
        project = Project(projectURL: currentURL.URLByAppendingPathComponent("test/AppCanPlugin/AppCanPlugin.xcodeproj"))
    }
    
    
    
    // MARK: 复制.framework到uexFrameworks目录
    func copyFramework() -> Void {
        print("copy framework to 'uexFrameworks'...")
        //要拷贝的framework的URL "./test/${frameworkName}"
        let frameworkURL = currentURL.URLByAppendingPathComponent("test").URLByAppendingPathComponent(frameworkName)
        guard fileMgr.fileExistsAtPath(frameworkURL.path ?? "") else {
            print("framework not exist")
            fatalError()
        }
        do{
            let targetURL = currentURL.URLByAppendingPathComponent("test/AppCanPlugin/AppCanPlugin/uexFrameworks").URLByAppendingPathComponent(frameworkName)
            try fileMgr.copyItemAtURL(frameworkURL, toURL: targetURL)
        }catch let e as NSError{
            print("copy framework error:\(e.localizedDescription)!")
            fatalError()
        }
    }
    
    
    
    
    // MARK: 添加FileReference
    
    /**
     添加.framework的FileReference到工程中
     
     - returns: 这个FileReference的UUID
     */
    func addFrameworkFileReference() -> String {
        print("add framework's file reference...")
        // 生成fileReference的uuid
        let uuid = UUIDGenerator.newUUID()
        // 生成fileReference的data
        let fileReferenceData = [
            "isa": "PBXFileReference",
            "lastKnownFileType": "wrapper.framework",
            "path": frameworkName,
            "sourceTree": "<group>"
        ]
        guard let project = project else{
            print("project not exist!")
            fatalError()
        }
        
        project.objects[uuid] = fileReferenceData
        //找到mainGroup -> AppCanPlugin -> uexFrameworks这个group
        guard let uexFrameworksGroup = project.mainGroup()
            .subgroup(withName: "AppCanPlugin")?
            .subgroup(withName: "uexFrameworks") else {
                print("uexFrameworks not found!")
                fatalError()
        }
        //往group的children中添加fileReference的uuid
        uexFrameworksGroup.children.append(uuid)
        //objects提交group的修改
        project.objects[uexFrameworksGroup.uuid] = uexFrameworksGroup.data
        //objects添加fileReference
        project.objects[uuid] = fileReferenceData
        //print("fileRef uuid:\(uuid)")
        return uuid
    }
    
    // MARK: 添加FileReference至Frameworks Build Phase
    
    /**
     将指定文件添加到PBXFrameworksBuildPhase的build phase中
     
     - parameter targetUUID: 要添加文件的fileReference的UUID
     */
    func addToFrameworksBuildPhase(frameworkUUID: String) -> Void {
        print("add framework to 'Link Binary With Libraries' build phase...")
        guard let project = project else{
            print("project not exist!")
            fatalError()
        }
        //生成buildFile的uuid
        let uuid = UUIDGenerator.newUUID()
        //生成buildFile的data
        let buildFileData = [
            "isa": "PBXBuildFile",
            "fileRef": frameworkUUID
        ]
        //找到frameworksBuildPhase
        guard var buildPhase = project.frameworksBuildPhase(inTarget: "AppCanPlugin") else {
            print("frameworksBuildPhase not found!")
            fatalError()
        }
        //往其中的"file"字段对应的数组中添加buildFile的uuid
        guard var files = buildPhase.data["files"] as? [String] else{
            print("frameworksBuildPhase data error!")
            fatalError()
        }
        files.append(uuid)
        buildPhase.data["files"] = files
        //objects提交buildPhase的修改
        project.objects[buildPhase.uuid] = buildPhase.data
        //objects添加buildFile
        project.objects[uuid] = buildFileData
    }
    
    // MARK: 添加FileReference至Embed Frameworks BuildPhase
    /**
     将指定文件添加到PBXFrameworksBuildPhase的build phase中
     
     - parameter frameworkUUID: 要添加文件的fileReference的UUID
     */
    func addToEmbedFrameworksBuildPhase(frameworkUUID: String) -> Void {
        print("add framework to 'Embed Frameworks' build phase...")
        guard let project = project else{
            print("project not exist!")
            fatalError()
        }
        //生成buildFile的uuid
        let uuid = UUIDGenerator.newUUID()
        //生成buildFile的data
        let buildFileData = [
            "isa": "PBXBuildFile",
            "fileRef": frameworkUUID,
            "settings": [
                "ATTRIBUTES": ["CodeSignOnCopy", "RemoveHeadersOnCopy"]
            ]
        ]
        //找到embedFrameworksBuildPhase
        guard var buildPhase = project.embedFrameworksBuildPhase(inTarget: "AppCanPlugin") else {
            print("frameworksBuildPhase not found!")
            fatalError()
        }
        //往其中的"file"字段对应的数组中添加buildFile的uuid
        guard var files = buildPhase.data["files"] as? [String] else{
            print("frameworksBuildPhase data error!")
            fatalError()
        }
        files.append(uuid)
        buildPhase.data["files"] = files
        //objects提交buildPhase的修改
        project.objects[buildPhase.uuid] = buildPhase.data
        //objects添加buildFile
        project.objects[uuid] = buildFileData
    }
    
    // MARK: 存储修改
    func commitChange() -> Void {
        print("commit change...")
        project?.save()
    }
    
}



// MARK: - 主程序
print("task begin")
let task = AddEmbedFrameworkTask()// task初始化
task.copyResources()// 复制测试用的资源文件
task.loadProject()// 加载工程
task.copyFramework()// 复制framework到指定目录
let frameworkRef = task.addFrameworkFileReference()// 工程中添加framework的file reference
task.addToFrameworksBuildPhase(frameworkRef)//工程中添加framework到fameworksBuildPhase
task.addToEmbedFrameworksBuildPhase(frameworkRef)//工程中添加framework到embedFameworksBuildPhase
task.commitChange()//存储修改
print("finish task!")
